<?php
/**
 * Copyright (c) [2019] [吴跃忠]
 * [selibra] is licensed under the Mulan PSL v1.
 * You can use this software according to the terms and conditions of the Mulan PSL v1.
 * You may obtain a copy of Mulan PSL v1 at:
 * http://license.coscl.org.cn/MulanPSL
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
 * PURPOSE.
 * See the Mulan PSL v1 for more details.
 */

namespace Selibra\WebSocket;

use Selibra\Tools\Console;
use Selibra\Di\Annotations\Autowired;
use Selibra\Di\Annotations\ServerAfterStartup;
use Selibra\Di\ContainerObjectInitialization;
use Selibra\Di\DI;
use Selibra\WebSocket\Annotations\WebSocketManagerStartComponent;
use Selibra\WebSocket\Protocols\WSServerInterface;
use Swoole\Process;
use Swoole\WebSocket\CloseFrame;
use Swoole\WebSocket\Server;

class WSServer implements WSServerInterface
{

    /**
     * @var CloseFrameNotice
     */
    protected CloseFrameNotice $closeFrameNotice;


    /**
     * @var WSActionDispatcher
     */
    #[Autowired]
    protected WSActionDispatcher $wsActionDispatcher;


    /**
     * @var Server
     */
    protected static Server $server;

    /**
     * @return Server
     * @throws \Exception
     */
    public static function getServer(): Server
    {
        if( !isset(self::$server) ){
            throw new \Exception("ServerError");
        }
        return self::$server;
    }

    /**
     * @param Server $server
     */
    public static function setServer(Server $server): void
    {
        self::$server = $server;
    }


    public function __construct()
    {
    }


    /**
     * 启动WebSocket服务
     * @return null
     * @throws \Exception
     */
    public function start()
    {
        $configs = (new WSConfig())->getConfigs();
        self::$server = new Server($configs['ip'], $configs['port']);
        self::$server->set($configs['server']);
        self::$server->on('start', function (Server $server) {
            $callbacks = WebSocketServerOnStartCallbackCollector::getCallbacks();
            foreach ($callbacks as $callback) {
                // 协程执行
                go(function () use ($callback, $server) {
                    $object = DI::getObjectContext($callback[0]);
                    $method = $callback[1];
                    call_user_func_array([$object, $method], [$server]);
                });
            }
        });

        self::$server->on('managerStart', function (Server $server) {
            ContainerObjectInitialization::componentMethodRun(WebSocketManagerStartComponent::class);
        });

        self::$server->on('task', function (Server $server, $data) {

        });

        self::$server->on('open', function (Server $server, $request) {

        });

        self::$server->on('message', function (Server $server, $frame) {
            if ($frame instanceof CloseFrame) {
                $this->close($server, $frame->fd, $frame);
            } elseif (!empty($frame->data)) {
                Console::log("WSMessage:" . $frame->data);
                // 消息解析器
                try {
                    $messageParser = new MessageParser();
                    $messageInput = $messageParser->parser($frame->data);

                    // 消息
                    $message = new Message();
                    $message->setFrame($frame);
                    $message->setMessageInput($messageInput);
                    $message->setServer($server);

                    // 调用
                    $this->wsActionDispatcher->run($message);

                } catch (\Exception $exception) {
//                    Console::log($exception);
                }
            }
            return true;
        });
        self::$server->on('close', function ($sever, $fd) {
            $this->close($sever, $fd);
        });
        self::getServer()->start();
    }


    /**
     * @param $server
     * @param $fd
     * @param null $closeFrame
     */
    protected function close($server, $fd, $closeFrame = null)
    {
        $this->closeFrameNotice = new CloseFrameNotice();
        $this->closeFrameNotice->notice($server, $fd, $closeFrame);
    }
}